/******************************************************************************
*
* This file is part of FreeTrack (http://code.google.com/p/qtfreetrack).
* BY Willy Gardiol (willy@gardiol.org)
*
* FreeTrack is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* FreeTrack is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Foobar.  If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/

#include "workerloadgpx.h"

WorkerLoadGpx::WorkerLoadGpx(const QString& filename, GPS_track* t, const QString &ti) :
    WorkerProcess(3,ti),
    reader(NULL),
    file(filename)
{
    setMinMaxProgress( file.size() );
    if ( file.open( QIODevice::ReadOnly ) )
            reader = new QXmlStreamReader( &file );
    track = t;
    n_points = n_legs = n_waypoints = 0;
    saved_string = file.fileName();
}

WorkerLoadGpx::WorkerLoadGpx(QByteArray& b,
                             GPS_track* t,
                             const QString& ti) :
    WorkerProcess(3, ti),
    reader(NULL)
{
    setMinMaxProgress( b.size() );
    reader = new QXmlStreamReader( b );
    track = t;
    n_points = n_legs = n_waypoints = 0;
    saved_string = tr("imported track");
}

WorkerLoadGpx::~WorkerLoadGpx()
{
    if ( reader != NULL )
        delete reader;
}

bool WorkerLoadGpx::updateWaypoints()
{
    updateText( 0, tr("Found %1 waypoints.").arg(n_waypoints) );
    updateProgress( reader->characterOffset() );
    return please_terminate;
}

bool WorkerLoadGpx::updatePoints()
{
    updateText( 2, tr("Found %1 points.").arg(n_points) );
    updateProgress( reader->characterOffset() );
    return please_terminate;
}

bool WorkerLoadGpx::updateLegs()
{
    updateText( 1, tr("Found %1 legs.").arg(n_legs) );
    updateProgress( reader->characterOffset() );
    return please_terminate;
}

bool WorkerLoadGpx::readNextNode( QString & node, QXmlStreamAttributes &attributes)
{
    while( reader->readNextStartElement() )
    {
        node = reader->name().toString().toLower();
        attributes = reader->attributes();
        return true;
    }
    return false;
}

bool WorkerLoadGpx::readNextNode(const QString & request, QString & node, QXmlStreamAttributes &attributes)
{
    while ( readNextNode( node, attributes ) )
    {
        if ( node == request )
            return true;
    }
    return false;
}

void WorkerLoadGpx::readNodeData(QString & str)
{
    str = reader->readElementText();
}

void WorkerLoadGpx::readNodeData(double & dbl)
{
    dbl = reader->readElementText().toDouble();
}

void WorkerLoadGpx::readNodeData(int & intg)
{
    intg = reader->readElementText().toInt();
}

void WorkerLoadGpx::readNodeData(QDateTime & qdt)
{
    QString str = reader->readElementText();
    qdt = QDateTime::fromString(str, Qt::ISODate);

}

void WorkerLoadGpx::readBounds(const QXmlStreamAttributes& attributes )
{
    double min_lat, min_lon, max_lat, max_lon;
    min_lat = attributes.value("minlat").toString().toDouble();
    min_lon = attributes.value("minlon").toString().toDouble();
    max_lat = attributes.value("maxlat").toString().toDouble();
    max_lon = attributes.value("maxlon").toString().toDouble();
    double w = fabs( max_lon - min_lon );
    double h = fabs( max_lat - min_lat );
    track->setBoundingRect( QRectF(qMin(min_lon, max_lon), qMin( min_lat, max_lat ), w, h) );
}

bool WorkerLoadGpx::do_stuff()
{
    QString version;
    QString creator;    

    updateText(0, tr("No waypoints found yet"));
    updateText(1, tr("No legs found yet"));
    updateText(2, tr("No points found yet"));

    if ( reader == NULL )
    {
        setErrorString( tr("Unable to read source!" ) );
        return false;
    }

    QString name;

    QXmlStreamAttributes attributes;
    QString node;
    bool title_read = false;
    bool description_read = false;

    if ( readNextNode( "gpx", node, attributes ) )
    {
        version = attributes.value("version").toString();
        creator = attributes.value("creator").toString();

        while ( readNextNode(node, attributes ) )
        {
            // These fields are common to 1.0 and 1.1
            if ( node == "wpt" )
            {
                readWaypoint( attributes.value("lat").toString().toDouble(),
                              attributes.value("lon").toString().toDouble() );
            }
            else if ( node == "rte" )
            {
                readRte();
            }
            else if ( node == "trk" )
            {
                readTrk();
            }

            // GPX 1.0 - these fields are at the main level
            else if ( node == "name" )
            {
                readNodeData( name );
                track->setTitle( name );
                title_read = true;
            }
            else if ( node == "desc" )
            {
                readNodeData( name );
                track->setDescription( name );
                description_read = true;
            }
            else if ( node == "bounds" )
            {
                readBounds(attributes);
            }

            // GPX 1.1 - puts all these fields inside "metadata"
            else if ( node == "metadata" )
            {
                while ( readNextNode(node, attributes ) )
                {
                    if ( node == "name" )
                    {
                        readNodeData( name );
                        track->setTitle( name );
                        title_read = true;
                    }
                    else if ( node == "desc" )
                    {
                        readNodeData( name );
                        track->setDescription( name );
                        description_read = true;
                    }
                    else if ( node == "bounds" )
                    {
                        readBounds(attributes);
                        reader->skipCurrentElement();
                    }
                    else // "link", "author", "copyright", "time", "keywords", "extensions"
                    {
                        reader->skipCurrentElement();
                    }
                }
            }
            else // skip any other data
            {
                reader->skipCurrentElement();
            }
            updateProgress( reader->characterOffset() );
            if ( please_terminate )
                return false;
        }
    }
    if ( !title_read )
    {
        track->setTitle( saved_string );
    }
    if ( !description_read )
    {
        track->setDescription( creator );
    }
    return true;
}

void WorkerLoadGpx::readRte()
{
    GPS_leg* newLeg = new GPS_leg();
    QString name;
    QString node;
    QXmlStreamAttributes attributes;

    while ( readNextNode(node, attributes ) )
    {
        if ( node == "name" )
        {
            readNodeData( name );
            newLeg->setTitle( name );
        }
        else if ( node == "rtept" )
        {
            // TODO: routes uses waypoints and not points!!!
            readPoint( newLeg,
                       attributes.value("lat").toString().toDouble(),
                       attributes.value("lon").toString().toDouble() );
        }
        else // for "cmt", "desc", "src", "link", "number", "type"
            reader->skipCurrentElement();
    }
    track->addLeg( newLeg );
    n_legs++;
    updateLegs();
}

void WorkerLoadGpx::readTrk()
{
    GPS_leg* newLeg = new GPS_leg();

    QString node;
    QString name;
    QColor color;

    QXmlStreamAttributes attributes;
    while ( readNextNode(node, attributes ) )
    {
        if ( node == "trkseg" )
        {
            readTrkSeg(newLeg);
        }
        else if ( node == "name" )
        {
            readNodeData( name );
            newLeg->setTitle( name );
        }
        else if ( node == "extensions" )
        {
            while ( readNextNode(node, attributes ) )
            {
                if ( node == "trackaway_color" )
                {
                    color = readColor( attributes );
                    newLeg->setColor( color );
                    reader->skipCurrentElement();
                }
                else
                    reader->skipCurrentElement();
            }
        }
        else // Ignore, "cmt", "desc", "src", "link", "number", "type", "extensions"
            reader->skipCurrentElement();
    }
    track->addLeg( newLeg );
    n_legs++;
    updateLegs();
}

void WorkerLoadGpx::readTrkSeg(GPS_leg* newLeg)
{
    QString node;
    QXmlStreamAttributes attributes;

    while ( readNextNode(node, attributes ) )
    {
        if ( node == "trkpt")
        {
            readPoint( newLeg,
                       attributes.value("lat").toString().toDouble(),
                       attributes.value("lon").toString().toDouble() );
        }
        else
            reader->skipCurrentElement();
    }
}

void WorkerLoadGpx::readPoint(GPS_leg * newLeg, double lat, double lon)
{
    GPS_point* newPt = new GPS_point();
    int hdop = -1;
    int vdop = -1;
    int pdop = -1;
    QDateTime timestamp;
    double speed;
    double magvar;
    double elevation;
    double direction;
    double steepness;
    QString node;
    int h,v;
    QXmlStreamAttributes attributes;

    newPt->setLatitude( lat );
    newPt->setLongitude( lon );

    while ( readNextNode(node, attributes ) )
    {
        if ( node == "ele" )
        {
            readNodeData( elevation );
            newPt->setAltitude( elevation );
        }
        else if ( node == "time" )
        {
            readNodeData( timestamp );
            newPt->setTimestamp( timestamp );
        }
        else if ( node == "magvar" )
        {
            readNodeData( magvar );
            newPt->setMagneticVariation( magvar );
        }
        else if ( node == "hdop" )
        {
            readNodeData( hdop );
        }
        else if ( node == "vdop" )
        {
            readNodeData( vdop );
        }
        else if ( node == "pdop" )
        {
            readNodeData( pdop );
        }
        // GPX 1.0 - speed is at this level
        else if ( node == "speed" )
        {
            readNodeData( speed );
            newPt->setSpeed( speed );
        }
        else if ( node == "extensions" )
        {
            while ( readNextNode(node, attributes ) )
            {
                // GPX 1.1 - no default speed, so we add an extension
                if ( node == "speed" )
                {
                    readNodeData( speed );
                    newPt->setSpeed( speed );
                }
                else if ( node == "trackaway_precision" )
                {
                    h = attributes.value("h_precision").toString().toInt();
                    v = attributes.value("v_precision").toString().toInt();
                    newPt->setPrecision(h,v);
                    reader->skipCurrentElement();
                }
                else if ( node == "trackaway_direction" )
                {
                    readNodeData( direction );
                    newPt->setDirection( direction );
                }
                else if ( node == "trackaway_steepness" )
                {
                    readNodeData( steepness );
                    newPt->setSteepness( steepness );
                }
                else
                    reader->skipCurrentElement();
            }
        }
        else // Ignore: "geoidheight", "cmt", "src", "link", "sym", "type", "fix", "sat",
            //          "ageofgpsdata", "dgpsid", "name", "desc", "cmt"
            reader->skipCurrentElement();
    }
    n_points++;
    updatePoints();
    GPS_dop newDop( hdop, vdop, pdop );
    newPt->setDop( newDop );
    newLeg->addPoint( newPt );
}

void WorkerLoadGpx::readWaypoint(double lat, double lon)
{
    GPS_waypoint* newWp = new GPS_waypoint();
    QString name;
    QColor color;
    double elevation;
    QString node;
    QXmlStreamAttributes attributes;

    newWp->setLatitude( lat );
    newWp->setLongitude( lon );

    while ( readNextNode(node, attributes ) )
    {
        if ( node == "ele" )
        {
            readNodeData( elevation );
            newWp->setAltitude( elevation );
        }
        else if ( node == "name" )
        {
            readNodeData( name );
            newWp->setName( name );
        }
        else if ( node == "desc" )
        {
            readNodeData( name );
            newWp->setDescription( name );
        }
        else if ( node == "extensions" )
        {
            while ( readNextNode(node, attributes ) )
            {
                if ( node == "trackaway_color" )
                {
                    color = readColor( attributes );
                    newWp->setColor( color );
                    reader->skipCurrentElement();
                }
                else
                    reader->skipCurrentElement();
            }
        }
        else // Ignore: "time", "magvar", "geoidheight", "cmt", "src", "link", "sym", "type", "fix", "sat", "hdop",
            // "vdop", "pdop", "ageofgpsdata", "dgpsid",
            reader->skipCurrentElement();
    }
    n_waypoints++;
    updateWaypoints();
    track->addWaypoint( newWp );
}

QColor WorkerLoadGpx::readColor(const QXmlStreamAttributes & attributes)
{
    return QColor( attributes.value("r").toString().toInt(),
                   attributes.value("g").toString().toInt(),
                   attributes.value("b").toString().toInt() );
}
